{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Working with snapshots\n", "\n", "Here, the following topics are going to be covered:\n", "\n", "- What is a snapshot\n", "- How to create it\n", "- How it is saved next to the measurement data\n", "- How to extract snapshot from the dataset" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Useful imports" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import json # for converting JSON data into python 'dict'\n", "from pathlib import Path\n", "from pprint import pprint # for pretty-printing python variables like 'dict'\n", "\n", "from qcodes.dataset import (\n", " Measurement,\n", " initialise_or_create_database_at,\n", " load_or_create_experiment,\n", ")\n", "from qcodes.instrument_drivers.mock_instruments import DummyInstrument\n", "from qcodes.parameters import Parameter\n", "from qcodes.station import Station" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is a snapshot?\n", "\n", "Often times experiments comprise a complex network of interconnected instruments. Their numerous settings define the overall behavior of the experimental setup. Obviously, the experimental setup has a direct impact on the measured data which is of prime interest for researchers. In order to capture this link, the measured data should have metadata associated with it. An important part of that metadata is a captured state of the experimental setup. In QCoDeS terms, this is called snapshot." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How to create a snapshot?\n", "\n", "All QCoDeS instruments and parameters (and some other objects too, like `InstrumentChannel`s) support snapshotting, which means that they provide a method to retrieve their state.\n", "\n", "Let's look at snapshots of various objects." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Snapshot example for Parameter object" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's create a `Parameter`, call its `snapshot` method, and then inspect the output of that method.\n", "\n", "The returned snapshot is a python dictionary that reflects all the important properties of that parameter (check the name, label, unit, and even value)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'p',\n", " 'inter_delay': 0,\n", " 'label': 'Parameter P',\n", " 'name': 'p',\n", " 'post_delay': 0,\n", " 'raw_value': 123,\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': 'kg',\n", " 'value': 123}\n" ] } ], "source": [ "p = Parameter('p', label='Parameter P', unit='kg', set_cmd=None, get_cmd=None)\n", "p.set(123)\n", "\n", "snapshot_of_p = p.snapshot()\n", "\n", "pprint(snapshot_of_p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In case you want to use the snapshot object in your code, you can refer to its contents in the same way as you work with python dictionaries, for example:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Value of Parameter P was 123 (when it was snapshotted).\n" ] } ], "source": [ "print(f\"Value of {snapshot_of_p['label']} was {snapshot_of_p['value']} (when it was snapshotted).\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the implementation of a particular QCoDeS object defines which attributes are snapshotted and how. For example, `Parameter` implements a keyword argument `snapshot_value` which allows to choose if the value of the parameter is snapshotted (the reasons for this are out of scope of this article). (Another interesting keyword argument of `Parameter` that is realated to snapshotting is `snapshot_get` - refer to `Parameters`'s docstring for more information.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below is a demonstration of the `snapshot_value` keyword argument, notice that the value of the parameter is not part of the snapshot." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'q',\n", " 'inter_delay': 0,\n", " 'label': 'Parameter Q',\n", " 'name': 'q',\n", " 'post_delay': 0,\n", " 'ts': None,\n", " 'unit': 'A'}\n" ] } ], "source": [ "q = Parameter('q', label='Parameter Q', unit='A', snapshot_value=False, set_cmd=None, get_cmd=None)\n", "p.set(456)\n", "\n", "snapshot_of_q = q.snapshot()\n", "\n", "pprint(snapshot_of_q)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Snapshot of an Instrument\n", "\n", "Now let's have a brief look at snapshots of instruments. For the sake of exercise, we are going to use a \"dummy\" instrument." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# A dummy instrument with two parameters, \"input\" and \"output\", plus a third one we'll use later.\n", "instr = DummyInstrument('instr', gates=['input', 'output', 'gain'])\n", "instr.gain(11)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{ '__class__': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'functions': {},\n", " 'name': 'instr',\n", " 'parameters': { 'IDN': { '__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_IDN',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'IDN',\n", " 'name': 'IDN',\n", " 'post_delay': 0,\n", " 'raw_value': None,\n", " 'ts': None,\n", " 'unit': '',\n", " 'vals': '',\n", " 'value': None},\n", " 'gain': { '__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_gain',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate gain',\n", " 'name': 'gain',\n", " 'post_delay': 0,\n", " 'raw_value': 11,\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 11},\n", " 'input': { '__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_input',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate input',\n", " 'name': 'input',\n", " 'post_delay': 0,\n", " 'raw_value': 0,\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 0},\n", " 'output': { '__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_output',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate output',\n", " 'name': 'output',\n", " 'post_delay': 0,\n", " 'raw_value': 0,\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 0}},\n", " 'submodules': {}}\n" ] } ], "source": [ "snapshot_of_instr = instr.snapshot()\n", "\n", "pprint(snapshot_of_instr, indent=4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Station and its snapshot\n", "\n", "Experimental setups are large, and instruments tend to be quite complex in that they comprise many parameters and other stateful parts. It would be very time-consuming for the user to manually go through every instrument and parameter, and collect the snapshot data.\n", "\n", "Here is where the concept of station comes into play. Instruments, parameters, and other submodules can be added to a [Station object](../Station.ipynb) ([nbviewer.jupyter.org link](https://nbviewer.jupyter.org/github/QCoDeS/Qcodes/tree/main/docs/examples/Station.ipynb)). In turn, the station has its `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules.\n", "\n", "Note that in this article the focus is on the snapshot feature of the QCoDeS `Station`, while it has some other features (also some legacy once)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's create a station, and add a parameter, instrument, and submodule to it. Then we will print the snapshot. Notice that the station is aware of insturments and stand-alone parameters, and classifies them into dedicated lists within the snapshot." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'instr'" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "station = Station()\n", "\n", "station.add_component(p)\n", "station.add_component(instr)\n", "# Note that it is also possible to add components\n", "# to a station via arguments of its constructor, like this:\n", "# station = Station(p, instr)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'components': {},\n", " 'default_measurement': [],\n", " 'instruments': {'instr': {'__class__': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'functions': {},\n", " 'name': 'instr',\n", " 'parameters': {'IDN': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_IDN',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'IDN',\n", " 'name': 'IDN',\n", " 'post_delay': 0,\n", " 'raw_value': {'firmware': None,\n", " 'model': 'instr',\n", " 'serial': None,\n", " 'vendor': None},\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': '',\n", " 'vals': '',\n", " 'value': {'firmware': None,\n", " 'model': 'instr',\n", " 'serial': None,\n", " 'vendor': None}},\n", " 'gain': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_gain',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate gain',\n", " 'name': 'gain',\n", " 'post_delay': 0,\n", " 'raw_value': 11,\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 11},\n", " 'input': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_input',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate input',\n", " 'name': 'input',\n", " 'post_delay': 0,\n", " 'raw_value': 0,\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 0},\n", " 'output': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_output',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate output',\n", " 'name': 'output',\n", " 'post_delay': 0,\n", " 'raw_value': 0,\n", " 'ts': '2019-06-21 '\n", " '13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 0}},\n", " 'submodules': {}}},\n", " 'parameters': {'p': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'p',\n", " 'inter_delay': 0,\n", " 'label': 'Parameter P',\n", " 'name': 'p',\n", " 'post_delay': 0,\n", " 'raw_value': 456,\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': 'kg',\n", " 'value': 456}}}\n" ] } ], "source": [ "snapshot_of_station = station.snapshot()\n", "\n", "pprint(snapshot_of_station)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Saving snapshot next to the measurement data\n", "\n", "With the power of the station object, it is now possible to conveniently associate the snapshot information with the measured data.\n", "\n", "In order to do so, a station needs to be created, and then that station needs to be provided to the `Measurement` object. If no station is explicitly provided, the `Measurement` object will use the default station, `Station.default` (refer to `Measurement` and `Station` objects docstrings for more information). At the moment the new measurement run is started, a snapshot of the whole station will be taken, and added next to the measured data.\n", "\n", "Note that the snapshot gets stored in a JSON format (an automatic convertion from python dictionary to JSON takes place). This is done in order to ensure that the snapshot can be read in environments other than python. JSON is an extemely popular data format, and all platforms/environments/languages/frameworks have means to read JSON-formatted data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is how it looks in the code. We will create a new experiment. Then we are going to reuse the station object created above, and create a new measurement object. Then we will perform a dummy measurement. After that we are going to extract the snapshot from the resulting dataset, and print it." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Let's initialize a database to ensure that it exists\n", "initialise_or_create_database_at(Path.cwd() / \"snapshot_example.db\")\n", "\n", "# Let's create a new experiment\n", "experiment = load_or_create_experiment('snapshot_experiment', 'no_sample_yet')" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "measurement = Measurement(experiment, station)\n", "\n", "measurement.register_parameter(instr.input)\n", "measurement.register_parameter(instr.output, setpoints=[instr.input])" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Starting experimental run with id: 5\n" ] } ], "source": [ "with measurement.run() as data_saver:\n", " input_value = 111\n", " instr.input.set(input_value)\n", "\n", " instr.output.set(222) # assuming that the instrument measured this value on the output\n", "\n", " data_saver.add_result((instr.input, input_value),\n", " (instr.output, instr.output()))\n", "\n", "# For convenience, let's work with the dataset object directly\n", "dataset = data_saver.dataset" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extracting snapshot from dataset" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we have a dataset that contains data from the measurement run. It also contains the snapshot.\n", "\n", "In order to access the snapshot, use the `DataSet`'s properties called `snapshot` and `snapshot_raw`. As their docstrings declare, the former returns the snapshot of the run as a python dictionary, while the latter returns it as JSON string (in other words, in exactly the same format as it is stored in the experiments database)." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "snapshot_of_run = dataset.snapshot" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "snapshot_of_run_in_json_format = dataset.snapshot_raw" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To prove that these snapshots are the same, use `json.loads` or `json.dumps` to assert the values of the variables:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "assert json.loads(snapshot_of_run_in_json_format) == snapshot_of_run" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "assert json.dumps(snapshot_of_run) == snapshot_of_run_in_json_format" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, let's pretty-print the snapshot. Notice that the values of the `input` and `output` parameters of the `instr` instrument have `0`s as values, and not `111` and `222` that were set during the measurement run." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'station': {'components': {},\n", " 'default_measurement': [],\n", " 'instruments': {'instr': {'__class__': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'functions': {},\n", " 'name': 'instr',\n", " 'parameters': {'IDN': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_IDN',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'IDN',\n", " 'name': 'IDN',\n", " 'post_delay': 0,\n", " 'raw_value': {'firmware': None,\n", " 'model': 'instr',\n", " 'serial': None,\n", " 'vendor': None},\n", " 'ts': '2019-06-21 '\n", " '13:10:37',\n", " 'unit': '',\n", " 'vals': '',\n", " 'value': {'firmware': None,\n", " 'model': 'instr',\n", " 'serial': None,\n", " 'vendor': None}},\n", " 'gain': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_gain',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate '\n", " 'gain',\n", " 'name': 'gain',\n", " 'post_delay': 0,\n", " 'raw_value': 11,\n", " 'ts': '2019-06-21 '\n", " '13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 11},\n", " 'input': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_input',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate '\n", " 'input',\n", " 'name': 'input',\n", " 'post_delay': 0,\n", " 'raw_value': 0,\n", " 'ts': '2019-06-21 '\n", " '13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 0},\n", " 'output': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'instr_output',\n", " 'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',\n", " 'instrument_name': 'instr',\n", " 'inter_delay': 0,\n", " 'label': 'Gate '\n", " 'output',\n", " 'name': 'output',\n", " 'post_delay': 0,\n", " 'raw_value': 0,\n", " 'ts': '2019-06-21 '\n", " '13:10:37',\n", " 'unit': 'V',\n", " 'vals': '',\n", " 'value': 0}},\n", " 'submodules': {}}},\n", " 'parameters': {'p': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'p',\n", " 'inter_delay': 0,\n", " 'label': 'Parameter P',\n", " 'name': 'p',\n", " 'post_delay': 0,\n", " 'raw_value': 456,\n", " 'ts': '2019-06-21 13:10:37',\n", " 'unit': 'kg',\n", " 'value': 456}}}}\n" ] } ], "source": [ "pprint(snapshot_of_run)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the snapshot that we have just loaded from the dataset is almost the same as the snapshot that we directly obtained from the station above. The only difference is that the snapshot loaded from the dataset has a top-level `station` field. If you do not trust me, have a look at the following `assert` statement for the proof." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "assert {'station': snapshot_of_station} == snapshot_of_run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Comparing how two DataSets were taken" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Suppose something went wrong in an experiment, and you'd like to compare what changed since a known-good run.\n", "QCoDeS lets you do this by taking a *diff* between the snapshots for two `DataSet` instances." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "measurement = Measurement(experiment, station)\n", "\n", "measurement.register_parameter(instr.input)\n", "measurement.register_parameter(instr.output, setpoints=[instr.input])" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Starting experimental run with id: 6\n" ] } ], "source": [ "instr.gain(400) # Oops!\n", "with measurement.run() as data_saver:\n", " input_value = 111\n", " instr.input.set(input_value)\n", "\n", " instr.output.set(222) # assuming that the instrument measured this value on the output\n", "\n", " data_saver.add_result((instr.input, input_value),\n", " (instr.output, instr.output()))\n", "\n", "# For convenience, let's work with the dataset object directly\n", "bad_dataset = data_saver.dataset" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `diff_param_values` function tells us about the parameters that changed betw" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "from qcodes.utils.metadata import diff_param_values" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{('instr', 'output'): (0, 222),\n", " ('instr', 'gain'): (11, 400),\n", " ('instr', 'input'): (0, 111)}" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "diff_param_values(dataset.snapshot, bad_dataset.snapshot).changed" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }